Package org.python.pydev.parser

Source Code of org.python.pydev.parser.PyParser$ParserInfo

/**
* Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
/*
* Author: atotic
* Created: Jul 25, 2003
*/
package org.python.pydev.parser;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.internal.resources.ResourceException;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.IRegion;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.texteditor.MarkerUtilities;
import org.python.pydev.core.ExtensionHelper;
import org.python.pydev.core.IGrammarVersionProvider;
import org.python.pydev.core.IPyEdit;
import org.python.pydev.core.IPythonNature;
import org.python.pydev.core.MisconfigurationException;
import org.python.pydev.core.Tuple3;
import org.python.pydev.core.log.Log;
import org.python.pydev.core.parser.ChangedParserInfoForObservers;
import org.python.pydev.core.parser.ErrorParserInfoForObservers;
import org.python.pydev.core.parser.IParserObserver;
import org.python.pydev.core.parser.IParserObserver2;
import org.python.pydev.core.parser.IParserObserver3;
import org.python.pydev.core.parser.IPyParser;
import org.python.pydev.parser.fastparser.FastParser;
import org.python.pydev.parser.grammar24.PythonGrammar24;
import org.python.pydev.parser.grammar25.PythonGrammar25;
import org.python.pydev.parser.grammar26.PythonGrammar26;
import org.python.pydev.parser.grammar27.PythonGrammar27;
import org.python.pydev.parser.grammar30.PythonGrammar30;
import org.python.pydev.parser.jython.FastCharStream;
import org.python.pydev.parser.jython.ParseException;
import org.python.pydev.parser.jython.SimpleNode;
import org.python.pydev.parser.jython.Token;
import org.python.pydev.parser.jython.TokenMgrError;
import org.python.pydev.parser.jython.ast.Module;
import org.python.pydev.parser.jython.ast.stmtType;

import com.aptana.shared_core.callbacks.ICallback;
import com.aptana.shared_core.io.FileUtils;
import com.aptana.shared_core.structure.Tuple;

/**
* PyParser uses org.python.parser to parse the document (lexical analysis) It
* is attached to PyEdit (a view), and it listens to document changes On every
* document change, the syntax tree is regenerated The reparsing of the document
* is done on a ParsingThread
*
* Clients that need to know when new parse tree has been generated should
* register as parseListeners.
*/

@SuppressWarnings("restriction")
public class PyParser implements IPyParser {

    /**
     * Just for tests: show whenever we're not able to parse some file.
     */
    public static boolean DEBUG_SHOW_PARSE_ERRORS = false;

    /**
     * just for tests, when we don't have any editor
     */
    public static boolean ACCEPT_NULL_INPUT_EDITOR = false;

    /**
     * Defines whether we should use the fast stream or not
     */
    public static boolean USE_FAST_STREAM = true;

    /**
     * this is the document we should parse
     */
    private volatile IDocument document;

    /**
     * ast for the last successful parsing
     */
    private SimpleNode root = null;

    /**
     * listens to changes in the document
     */
    private IDocumentListener documentListener;

    /**
     * listeners that get notified of successful or unsuccessful parser achievements
     */
    private ArrayList<IParserObserver> parserListeners;

    /**
     * used to enable tracing in the grammar
     */
    public static boolean ENABLE_TRACING = false;

    /**
     * this is the object that will keep parser schedules for us (and will call us for doing parsing when requested)
     */
    private ParserScheduler scheduler;

    /**
     * indicates we should do analysis only on doc save
     */
    private boolean useAnalysisOnlyOnDocSave;

    /**
     * This is the version of the grammar that should be used for this parser
     */
    private final IGrammarVersionProvider grammarVersionProvider;

    /**
     * Identifies whether this parser is disposed.
     */
    private volatile boolean disposed = false;

    public static String getGrammarVersionStr(int grammarVersion) {
        if (grammarVersion == IGrammarVersionProvider.GRAMMAR_PYTHON_VERSION_2_4) {
            return "grammar: Python 2.4";

        } else if (grammarVersion == IGrammarVersionProvider.GRAMMAR_PYTHON_VERSION_2_5) {
            return "grammar: Python 2.5";

        } else if (grammarVersion == IGrammarVersionProvider.GRAMMAR_PYTHON_VERSION_2_6) {
            return "grammar: Python 2.6";

        } else if (grammarVersion == IGrammarVersionProvider.GRAMMAR_PYTHON_VERSION_2_7) {
            return "grammar: Python 2.7";

        } else if (grammarVersion == IGrammarVersionProvider.GRAMMAR_PYTHON_VERSION_3_0) {
            return "grammar: Python 3.0";

        } else if (grammarVersion == IGrammarVersionProvider.GRAMMAR_PYTHON_VERSION_CYTHON) {
            return "grammar: Cython";

        } else {
            return "grammar: unrecognized: " + grammarVersion;
        }
    }

    public int getGrammarVersion() throws MisconfigurationException {
        return grammarVersionProvider.getGrammarVersion();
    }

    /**
     * Should only be called for testing. Does not register as a thread.
     */
    public PyParser(IGrammarVersionProvider grammarVersionProvider) {
        if (grammarVersionProvider == null) {
            grammarVersionProvider = new IGrammarVersionProvider() {
                public int getGrammarVersion() {
                    return IPythonNature.LATEST_GRAMMAR_VERSION;
                }
            };
        }
        this.grammarVersionProvider = grammarVersionProvider;
        parserListeners = new ArrayList<IParserObserver>();
        scheduler = new ParserScheduler(this);

        documentListener = new IDocumentListener() {

            public void documentChanged(DocumentEvent event) {
                if (useAnalysisOnlyOnDocSave) {
                    //if we're doing analysis only on doc change, the parser will not give any changes
                    //to the scheduler, so, we won't have any parse events to respond to
                    return;

                }
                String text = event.getText();

                boolean parseNow = true;
                if (event == null || text == null) {
                    parseNow = false;
                }
                if (parseNow) {
                    if (text.indexOf("\n") == -1 && text.indexOf("\r") == -1) {
                        parseNow = false;

                    }
                }

                if (!parseNow) {
                    // carriage return in changed text means parse now, anything
                    // else means parse later
                    scheduler.parseLater();
                } else {
                    scheduler.parseNow();
                }
            }

            public void documentAboutToBeChanged(DocumentEvent event) {
            }
        };

    }

    /**
     * Ok, create the parser for an editor
     *
     * @param editorView
     */
    public PyParser(IPyEdit editorView) {
        this(getGrammarProviderFromEdit(editorView));
    }

    /**
     * @param editorView this is the editor that we're getting in the parser
     * @return a provider signaling the grammar to be used for the parser.
     * @throws MisconfigurationException
     */
    private static IGrammarVersionProvider getGrammarProviderFromEdit(IPyEdit editorView) {
        return editorView.getGrammarVersionProvider();
    }

    /**
     * should be called when the editor is disposed
     */
    public void dispose() {
        this.disposed = true;
        this.scheduler.dispose();

        // remove the listeners
        if (document != null) {
            document.removeDocumentListener(documentListener);
        }
        synchronized (parserListeners) {
            parserListeners.clear();
        }
    }

    public SimpleNode getRoot() {
        return root;
    }

    public void notifySaved() {
        //force parse on save
        forceReparse();
    }

    /**
     * @return false if we asked a reparse and it will not be scheduled because a reparse is already in action.
     */
    public boolean forceReparse(Object... argsToReparse) {
        if (disposed) {
            return true; //reparse didn't happen, but no matter what happens, it won't happen anyways
        }
        return scheduler.parseNow(true, argsToReparse);
    }

    /**
     * This is the input from the editor that we're using in the parse
     */
    private IEditorInput input;

    public void setDocument(IDocument document, IEditorInput input) {
        setDocument(document, true, input);
    }

    public synchronized void setDocument(IDocument doc, boolean addToScheduler, IEditorInput input) {
        this.input = input;
        // Cleans up old listeners
        if (this.document != null) {
            this.document.removeDocumentListener(documentListener);
        }

        // Set up new listener
        this.document = doc;
        if (doc == null) {
            Log.log("No document in PyParser::setDocument?");
            return;
        }

        doc.addDocumentListener(documentListener);

        if (addToScheduler) {
            // Reparse document on the initial set (force it)
            scheduler.parseNow(true);
        }
    }

    // ---------------------------------------------------------------------------- listeners
    /** stock listener implementation */
    public void addParseListener(IParserObserver listener) {
        Assert.isNotNull(listener);
        synchronized (parserListeners) {
            if (!parserListeners.contains(listener)) {
                parserListeners.add(listener);
            }
        }
    }

    /** stock listener implementation */
    public void removeParseListener(IParserObserver listener) {
        Assert.isNotNull(listener);
        synchronized (parserListeners) {
            parserListeners.remove(listener);
        }
    }

    // ---------------------------------------------------------------------------- notifications
    /**
     * stock listener implementation event is fired whenever we get a new root
     * @param original
     */
    @SuppressWarnings("unchecked")
    protected void fireParserChanged(ChangedParserInfoForObservers info) {
        this.root = (SimpleNode) info.root;
        List<IParserObserver> temp;
        synchronized (parserListeners) {
            temp = new ArrayList<IParserObserver>(parserListeners);
        }

        for (IParserObserver l : temp) {
            try {
                //work on a copy (because listeners may want to remove themselves and we cannot afford concurrent modifications here)
                if (l instanceof IParserObserver3) {
                    ((IParserObserver3) l).parserChanged(info);

                } else if (l instanceof IParserObserver2) {
                    ((IParserObserver2) l).parserChanged(info.root, info.file, info.doc, info.argsToReparse);

                } else {
                    l.parserChanged(info.root, info.file, info.doc);
                }
            } catch (Exception e) {
                Log.log(e);
            }
        }

        List<IParserObserver> participants = ExtensionHelper.getParticipants(ExtensionHelper.PYDEV_PARSER_OBSERVER);
        for (IParserObserver observer : participants) {
            try {
                if (observer instanceof IParserObserver3) {
                    ((IParserObserver3) observer).parserChanged(info);

                } else if (observer instanceof IParserObserver2) {
                    ((IParserObserver2) observer).parserChanged(info.root, info.file, info.doc, info.argsToReparse);

                } else {
                    observer.parserChanged(info.root, info.file, info.doc);
                }
            } catch (Exception e) {
                Log.log(e);
            }
        }
    }

    /**
     * stock listener implementation event is fired when parse fails
     * @param original
     */
    @SuppressWarnings("unchecked")
    protected void fireParserError(ErrorParserInfoForObservers info) {
        List<IParserObserver> temp;
        synchronized (parserListeners) {
            temp = new ArrayList<IParserObserver>(parserListeners);
        }
        for (IParserObserver l : temp) {//work on a copy (because listeners may want to remove themselves and we cannot afford concurrent modifications here)
            if (l instanceof IParserObserver3) {
                ((IParserObserver3) l).parserError(info);

            } else if (l instanceof IParserObserver2) {
                ((IParserObserver2) l).parserError(info.error, info.file, info.doc, info.argsToReparse);

            } else {
                l.parserError(info.error, info.file, info.doc);
            }
        }
        List<IParserObserver> participants = ExtensionHelper.getParticipants(ExtensionHelper.PYDEV_PARSER_OBSERVER);
        for (IParserObserver observer : participants) {
            if (observer instanceof IParserObserver3) {
                ((IParserObserver3) observer).parserError(info);

            } else if (observer instanceof IParserObserver2) {
                ((IParserObserver2) observer).parserError(info.error, info.file, info.doc, info.argsToReparse);

            } else {
                observer.parserError(info.error, info.file, info.doc);
            }
        }
    }

    // ---------------------------------------------------------------------------- parsing

    /**
     * Parses the document, generates error annotations
     *
     * @param argsToReparse: will be passed to fireParserError / fireParserChanged so that the IParserObserver2
     * can check it. This is useful when the reparse was done with some specific thing in mind, so that its requestor
     * can pass some specific thing to the parser observers
     *
     * @return a tuple with the SimpleNode root(if parsed) and the error (if any).
     *         if we are able to recover from a reparse, we have both, the root and the error.
     */
    public Tuple<SimpleNode, Throwable> reparseDocument(Object... argsToReparse) {

        //get the document ast and error in object
        int version;
        try {
            version = grammarVersionProvider.getGrammarVersion();
        } catch (MisconfigurationException e1) {
            //Ok, we cannot get it... let's put on the default
            version = IGrammarVersionProvider.LATEST_GRAMMAR_VERSION;
        }
        long documentTime = System.currentTimeMillis();
        Tuple<SimpleNode, Throwable> obj = reparseDocument(new ParserInfo(document, version, true));

        IFile original = null;
        IAdaptable adaptable = null;

        if (input == null) {
            return obj;
        }

        original = (input instanceof IFileEditorInput) ? ((IFileEditorInput) input).getFile() : null;
        if (original != null) {
            adaptable = original;

        } else {
            //probably an external file, may have some location provider mechanism
            //it may be org.eclipse.ui.internal.editors.text.JavaFileEditorInput
            adaptable = input;
        }

        //delete the markers
        if (original != null) {
            try {
                deleteErrorMarkers(original);
            } catch (ResourceException e) {
                //ok, if it is a resource exception, it may have happened because the resource does not exist anymore
                //so, there is no need to log this failure
                if (original.exists()) {
                    Log.log(e);
                }
            } catch (CoreException e) {
                Log.log(e);
            }

        } else if (adaptable == null) {
            //ok, we have nothing... maybe we are in tests...
            if (!PyParser.ACCEPT_NULL_INPUT_EDITOR) {
                throw new RuntimeException("Null input editor received in parser!");
            }
        }
        //end delete the markers

        if (disposed) {
            //if it was disposed in this time, don't fire any notification nor return anything valid.
            return new Tuple<SimpleNode, Throwable>(null, null);
        }

        if (obj.o1 != null) {
            //Ok, reparse successful, lets erase the markers that are in the editor we just parsed
            //Note: we may get the ast even if errors happen (and we'll notify in that case too).
            ChangedParserInfoForObservers info = new ChangedParserInfoForObservers(obj.o1, adaptable, document,
                    documentTime, argsToReparse);
            fireParserChanged(info);
        }

        if (obj.o2 instanceof ParseException || obj.o2 instanceof TokenMgrError) {
            ErrorParserInfoForObservers info = new ErrorParserInfoForObservers(obj.o2, adaptable, document,
                    argsToReparse);
            fireParserError(info);
        }

        return obj;
    }

    /**
     * This function will remove the markers related to errors.
     * @param resource the file that should have the markers removed
     * @throws CoreException
     */
    public static void deleteErrorMarkers(IResource resource) throws CoreException {
        IMarker[] markers = resource.findMarkers(IMarker.PROBLEM, false, IResource.DEPTH_ZERO);
        if (markers.length > 0) {
            resource.deleteMarkers(IMarker.PROBLEM, false, IResource.DEPTH_ZERO);
        }
    }

    //static methods that can be used to get the ast (and error if any) --------------------------------------

    public final static class ParserInfo {
        public IDocument document;

        /**
         * A set with the lines that were changed when trying to make the document parseable
         */
        public final Set<Integer> linesChanged = new HashSet<Integer>();

        /**
         * This is the version of the grammar to be used
         * @see IPythonNature.GRAMMAR_XXX constants
         */
        public final int grammarVersion;

        /**
         * The module name of the contents parsed (may be null)
         */
        public final String moduleName;

        /**
         * The file that's been parsed (may be null)
         */
        public final File file;

        /**
         * Whether we should generate the tree as a parse result or we're just interested in errors.
         */
        public final boolean generateTree;

        /**
         * @param grammarVersion: see IPythonNature.GRAMMAR_XXX constants
         */
        public ParserInfo(IDocument document, int grammarVersion) {
            this(document, grammarVersion, null, null, true);
        }

        public ParserInfo(IDocument document, IGrammarVersionProvider nature) throws MisconfigurationException {
            this(document, nature.getGrammarVersion());
        }

        public ParserInfo(IDocument document, IGrammarVersionProvider nature, String moduleName, File file)
                throws MisconfigurationException {
            this(document, nature.getGrammarVersion(), moduleName, file, true);
        }

        public ParserInfo(IDocument document, int grammarVersion, String name, File f, boolean generateTree) {
            this.document = document;
            this.grammarVersion = grammarVersion;
            this.moduleName = name;
            this.file = f;
            this.generateTree = generateTree;
        }

        public ParserInfo(IDocument document, IGrammarVersionProvider grammarProvider, boolean generateTree)
                throws MisconfigurationException {
            this(document, grammarProvider.getGrammarVersion(), null, null, generateTree);
        }

        public ParserInfo(IDocument document, int grammarVersion, boolean generateTree) {
            this(document, grammarVersion, null, null, generateTree);
        }

        public String toString() {
            StringBuffer buf = new StringBuffer();
            buf.append("ParserInfo [");
            buf.append("file:");
            buf.append(file);
            buf.append("\nmoduleName:");
            buf.append(moduleName);
            if (!generateTree) {
                buf.append(" NOT GENERATING TREE");
            }
            buf.append("]");
            return buf.toString();
        }
    }

    /**
     * This list of callbacks is mostly used for testing, so that we can check what's been parsed.
     */
    public final static List<ICallback<Object, Tuple3<SimpleNode, Throwable, ParserInfo>>> successfulParseListeners = new ArrayList<ICallback<Object, Tuple3<SimpleNode, Throwable, ParserInfo>>>();

    /**
     * Create the char array to parse based on the initial document and our parser limitations.
     */
    private static char[] createCharArrayToParse(String startDoc) {
        int length = startDoc.length();
        int skipAtStart = 0;
        if (startDoc.startsWith(FileUtils.BOM_UTF8)) {
            skipAtStart = FileUtils.BOM_UTF8.length();
        } else if (startDoc.startsWith(FileUtils.BOM_UNICODE)) {
            skipAtStart = FileUtils.BOM_UNICODE.length();
        }

        int addAtEnd = 0;
        if (!startDoc.endsWith("\n") && !startDoc.endsWith("\r")) {
            addAtEnd = 1;
        }

        char[] charArray = new char[length - skipAtStart + addAtEnd];
        startDoc.getChars(skipAtStart, length, charArray, 0);
        if (addAtEnd > 0) {
            charArray[charArray.length - 1] = '\n';
        }
        return charArray;
    }

    /**
     * Actually creates the grammar.
     * @param generateTree whether we should generate the AST or not.
     */
    private static IGrammar createGrammar(boolean generateTree, int grammarVersion, char[] charArray) {
        IGrammar grammar;
        FastCharStream in = new FastCharStream(charArray);
        switch (grammarVersion) {
            case IPythonNature.GRAMMAR_PYTHON_VERSION_2_4:
                grammar = new PythonGrammar24(generateTree, in);
                break;
            case IPythonNature.GRAMMAR_PYTHON_VERSION_2_5:
                grammar = new PythonGrammar25(generateTree, in);
                break;
            case IPythonNature.GRAMMAR_PYTHON_VERSION_2_6:
                grammar = new PythonGrammar26(generateTree, in);
                break;
            case IPythonNature.GRAMMAR_PYTHON_VERSION_2_7:
                grammar = new PythonGrammar27(generateTree, in);
                break;
            case IPythonNature.GRAMMAR_PYTHON_VERSION_3_0:
                grammar = new PythonGrammar30(generateTree, in);
                break;
            //case CYTHON: not treated here (only in reparseDocument).
            default:
                throw new RuntimeException("The grammar specified for parsing is not valid: " + grammarVersion);
        }

        if (ENABLE_TRACING) {
            //grammar has to be generated with debugging info for this to make a difference
            grammar.enable_tracing();
        }
        return grammar;
    }

    /**
     * Note: this method should generally not be needed. Use reparseDocument on most situation (this
     * is mostly for tests or profilings).
     */
    public static Tuple<SimpleNode, IGrammar> reparseDocumentInternal(IDocument doc, boolean generateTree,
            int grammarVersion)
            throws ParseException {
        char[] charArray = createCharArrayToParse(doc.get());
        IGrammar grammar = createGrammar(generateTree, grammarVersion, charArray);
        return new Tuple<SimpleNode, IGrammar>(grammar.file_input(), grammar); // parses the file
    }

    /**
     * @return a tuple with the SimpleNode root(if parsed) and the error (if any).
     *         if we are able to recover from a reparse, we have both, the root and the error.
     */
    public static Tuple<SimpleNode, Throwable> reparseDocument(ParserInfo info) {
        if (info.grammarVersion == IPythonNature.GRAMMAR_PYTHON_VERSION_CYTHON) {
            IDocument doc = info.document;
            return createCythonAst(doc);
        }

        // create a stream with document's data
        String startDoc = info.document.get();
        if (startDoc.trim().length() == 0) {
            //If empty, don't bother to parse!
            return new Tuple<SimpleNode, Throwable>(new Module(new stmtType[0]), null);
        }
        char[] charArray = createCharArrayToParse(startDoc);
        startDoc = null; //it can be garbage-collected now.

        Tuple<SimpleNode, Throwable> returnVar = new Tuple<SimpleNode, Throwable>(null, null);
        IGrammar grammar = null;
        try {
            grammar = createGrammar(info.generateTree, info.grammarVersion, charArray);
            SimpleNode newRoot = grammar.file_input(); // parses the file
            returnVar.o1 = newRoot;

            //only notify successful parses
            if (successfulParseListeners.size() > 0) {
                Tuple3<SimpleNode, Throwable, ParserInfo> param = new Tuple3<SimpleNode, Throwable, ParserInfo>(
                        returnVar.o1, returnVar.o2, info);

                for (ICallback<Object, Tuple3<SimpleNode, Throwable, ParserInfo>> callback : successfulParseListeners) {
                    callback.call(param);
                }
            }

            returnVar.o2 = grammar.getErrorOnParsing();

        } catch (Throwable e) {
            //ok, some error happened when trying the parse... let's go and clear the local info before doing
            //another parse.
            if (DEBUG_SHOW_PARSE_ERRORS) {
                e.printStackTrace();
            }

            //If the grammar was not created, the problem wasn't in the parsing... so, let's just rethrow the error
            if (grammar == null) {
                throw new RuntimeException(e);
            }

            //We have to change it for the 1st error we got (the one in catch is the last one).
            Throwable errorOnParsing = grammar.getErrorOnParsing();
            if (errorOnParsing != null) {
                e = errorOnParsing;
            } else if (DEBUG_SHOW_PARSE_ERRORS) {
                System.out.println("Unhandled error");
                e.printStackTrace();
            }

            grammar = null;

            if (e instanceof ParseException || e instanceof TokenMgrError) {
                returnVar = new Tuple<SimpleNode, Throwable>(null, e);

            } else if (e.getClass().getName().indexOf("LookaheadSuccess") != -1) {
                //don't log this kind of error...
            } else {
                Log.log(e);
            }

        }

        if (DEBUG_SHOW_PARSE_ERRORS) {
            if (returnVar.o1 == null) {
                System.out.println("Unable to parse " + info);
            }
        }
        //        System.out.println("Output grammar: "+returnVar);
        return returnVar;
    }

    public static Tuple<SimpleNode, Throwable> createCythonAst(IDocument doc) {
        List<stmtType> classesAndFunctions = FastParser.parseCython(doc);
        return new Tuple<SimpleNode, Throwable>(new Module(classesAndFunctions.toArray(new stmtType[classesAndFunctions
                .size()])), null);
    }

    public void resetTimeoutPreferences(boolean useAnalysisOnlyOnDocSave) {
        this.useAnalysisOnlyOnDocSave = useAnalysisOnlyOnDocSave;
    }

    public List<IParserObserver> getObservers() {
        synchronized (parserListeners) {
            return new ArrayList<IParserObserver>(this.parserListeners);
        }
    }

    /**
     * Adds the error markers for some error that was found in the parsing process.
     *
     * @param error the error find while parsing the document
     * @param resource the resource that should have the error added
     * @param doc the document with the resource contents
     * @return the error description (or null)
     *
     * @throws BadLocationException
     * @throws CoreException
     */
    public static ErrorDescription createParserErrorMarkers(Throwable error, IAdaptable resource, IDocument doc)
            throws BadLocationException, CoreException {
        ErrorDescription errDesc;
        if (resource == null) {
            return null;
        }
        IResource fileAdapter = (IResource) resource.getAdapter(IResource.class);
        if (fileAdapter == null) {
            return null;
        }

        errDesc = createErrorDesc(error, doc);

        Map<String, Object> map = new HashMap<String, Object>();

        map.put(IMarker.MESSAGE, errDesc.message);
        map.put(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
        map.put(IMarker.LINE_NUMBER, errDesc.errorLine);
        map.put(IMarker.CHAR_START, errDesc.errorStart);
        map.put(IMarker.CHAR_END, errDesc.errorEnd);
        map.put(IMarker.TRANSIENT, true);
        MarkerUtilities.createMarker(fileAdapter, map, IMarker.PROBLEM);
        return errDesc;
    }

    /**
     * Creates the error description for a given error in the parse.
     */
    private static ErrorDescription createErrorDesc(Throwable error, IDocument doc) throws BadLocationException {
        int errorStart = -1;
        int errorEnd = -1;
        int errorLine = -1;
        String message = null;
        if (error instanceof ParseException) {
            ParseException parseErr = (ParseException) error;

            // Figure out where the error is in the document, and create a
            // marker for it
            if (parseErr.currentToken == null) {
                IRegion endLine = doc.getLineInformationOfOffset(doc.getLength());
                errorStart = endLine.getOffset();
                errorEnd = endLine.getOffset() + endLine.getLength();

            } else {
                Token errorToken = parseErr.currentToken.next != null ? parseErr.currentToken.next
                        : parseErr.currentToken;
                IRegion startLine = doc.getLineInformation(getDocPosFromAstPos(errorToken.beginLine));
                IRegion endLine;
                if (errorToken.endLine == 0) {
                    endLine = startLine;
                } else {
                    endLine = doc.getLineInformation(getDocPosFromAstPos(errorToken.endLine));
                }
                errorStart = startLine.getOffset() + getDocPosFromAstPos(errorToken.beginColumn);
                errorEnd = endLine.getOffset() + errorToken.endColumn;
            }
            message = parseErr.getMessage();

        } else if (error instanceof TokenMgrError) {
            TokenMgrError tokenErr = (TokenMgrError) error;
            IRegion startLine = doc.getLineInformation(tokenErr.errorLine - 1);
            errorStart = startLine.getOffset();
            errorEnd = startLine.getOffset() + tokenErr.errorColumn;
            message = tokenErr.getMessage();
        } else {
            Log.log("Error, expecting ParseException or TokenMgrError. Received: " + error);
            return new ErrorDescription(null, 0, 0, 0);
        }
        errorLine = doc.getLineOfOffset(errorStart);

        // map.put(IMarker.LOCATION, "Whassup?"); this is the location field
        // in task manager
        if (message != null) { // prettyprint
            message = message.replaceAll("\\r\\n", " ");
            message = message.replaceAll("\\r", " ");
            message = message.replaceAll("\\n", " ");
        }

        return new ErrorDescription(message, errorLine, errorStart, errorEnd);
    }

    /**
     * The ast position starts at 1 and the document starts at 0 (but it could be that we had nothing valid
     * and received an invalid position, so, we must treat that).
     */
    private static int getDocPosFromAstPos(int astPos) {
        if (astPos > 0) {
            astPos--;
        }
        return astPos;
    }

}
TOP

Related Classes of org.python.pydev.parser.PyParser$ParserInfo

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.